/**
 * @file 组件对象池化
 * @author DuskyHuang 圣子
*/

import { _decorator, Component, Prefab, Node, instantiate } from 'cc';

/** 对象池 */
export interface IPool<InitArgs extends any[] = unknown[]> {
    readonly size: number;
    claim(...args: InitArgs): IPoolable;
    free(item: IPoolable): void;
    clear(): void;
    dump(): void;
    template?: Prefab;
    container?: Node;
}

/** 对象池对象 */
export interface IPoolable {
    claimed?(...args: any[]): void;
    freed?(): void;
    free?(): void;
}

/** 实体对象池类 */
export class ItemPool<T extends IPoolable, InitArgs extends any[] = unknown[]> implements IPool<InitArgs> {

    public constructor(ItemClass: ConcreteClass<T>, prefab?: Prefab, container?: Node) {
        this._ItemClass = ItemClass;
        this.template = prefab ?? this.template;
        this.container = container ?? this.container;
    }
    public template: Prefab = null;
    public container: Node = null;
    public get size(): num.int { return this.__pool.length; }

    public claim(...args: InitArgs): T {
        let item = this.__pool.pop() ?? this._create;
        if (item instanceof Component && this.container instanceof Node) {
            item.node.parent = this.container;
        }
        Function.invoke(item.claimed, item, ...args);
        return item;
    }

    public free(item: T): void {
        if (!item) return;
        if (item instanceof Component && !!item.node?.parent) {
            item.node.removeFromParent();
        }
        Function.invoke(item.freed, item);
        this.__pool.push(item);
    }

    public clear(): void {
        this.__pool.forEach(item => {
            if (item instanceof Component) {
                (item.node ?? item).destroy();
            }
        });
        this.__pool.length = 0;
    }

    public dump(): void {
        console.info(`EntityPool<${this._ItemClass.name}>, size=${this.size}`);
    }

    private _ItemClass: ConcreteClass<T> = null;
    private __pool: T[] = [];
    private get _create(): T {
        const { _ItemClass: _Item, template } = this;
        if (_Item.prototype instanceof Component) {
            const node = template instanceof Prefab ? instantiate(template) : new Node;
            const Comp = _Item as any;
            return (node.getComponent(Comp) ?? node.addComponent(Comp)) as unknown as T;
        } else return new _Item();
    }
}